iT邦幫忙

2024 iThome 鐵人賽

DAY 3
0
JavaScript

Don't make JavaScript Just Surpise系列 第 3

原始型別與複合型別(Primitive Type and Complex Type)

  • 分享至 

  • xImage
  •  

不管是哪種語言,變數都是重要的基礎概念。
理解程式中的型別選項與特性,才能更好的上手一門語言。
在討論型別之前,我們先從定義「變數」與「型別」開始。

「變數」可以抽象地理解為一個儲存值或物件的容器,實際上,它會指向某個記憶體位址,記憶體位址有著對應的值儲存。
「型別」則是規範變數所指向對象的行為或特性的分類。

JavaScript 是一個弱型別語言,也稱為動態語言。這意味著變數在宣告時賦予的型別能夠在執行時被更改。例如,某變數現在存的是字串,稍後可能會改為存入數字。

在 ECMAScript 規範中,JavaScript 定義了以下幾種「原始型別」(Primitive Type):

  • null
  • undefined
  • boolean
  • number
  • string
  • symbol(在 ES2015,ES6 中引入)
  • bigint(在 ES2020 中引入)

原始型別之外、其他型別皆屬於複合型別(Complex Type),常見的有以下幾種:

  • Object
  • Function
  • Array

原始型別與複合型別的主要差異在於:

  1. 可變性:
    在屬性和方法上,原始型別不可變,而複合型別是可變的。

     let a = 'abc';
     a.attr1 = 2;
     console.log(a.attr1);//undefined
    
     let b = {};
     b.attr1 = 2;
     console.log(b.attr1);//2
    
  2. 傳遞方式:
    在網路上經常會看到「傳值」和「傳址」的討論 - 通常認為,原始型別是「傳值」,而複合型別是「傳址」。這裡我們先把名詞放一旁,先著重於行為的討論,透過下面的程式碼,我們能觀察到兩種不同的行為:

    function change(a, b) {
        a += 5;
        b.val += 5;
    }
    let a = 10;
    let b = { val: 10 };
    console.log('before', a, b);
    change(a, b);
    console.log('after', a, b);
    

    上面程式碼輸出結果會是:

    "before", 10, { val: 10 }
    "after", 10, { val: 15 }
    

    這個例子展示了,當傳入的是「原始型別」時,函數內的操作不會影響外部的變數值;但對於「複合型別」,函數內的修改則會反映到外部變數。

    先看原始型別的第一個例子(a),外部作用域的 a 是一個指向記憶體中存放數值 10 的指標,在 change 函式內部,a 則是一個新的變數(與外部的 a 無關),指向同一個初始值 10 的位址,但當執行 a += 5 時,JavaScript 創建了一個新的記憶體位置來存放 15,並將內部的 a 指向該位址,外部的 a 指向的位址並未被這一改動調整。

    對於複合型別的 b,情況則不同。實際上傳入 change 內部時,是傳入了外部 b.val 指向的位址,也就是內部在修改值的時候,實際上是把傳入的位址指向了新的 15。因為內部與外部 b.val 指向的是同一記憶體位址。這就是為什麼結果中 b.val 變為 15 的原因。

    如果這段概念有些難理解,可以嘗試將變數和記憶體位址關係畫成圖表,左邊是變數,右邊是記憶體位址,最右邊是儲存的值。畫出變量與記憶體位址之間的關係,應該會更清晰。

  3. 屬性和方法:
    原始型別並不具備屬性和方法,而複合型別可以擁有多種屬性和方法。
    可能有人會問:字串有長度屬性length,這樣還能算是沒有屬性嗎?

    事實上,原始型別的字串在背後會自動轉換為一個 String 包裝物件。這個過程是由 JS 在背景處理的,當訪問 a.length 時,JS 會創建一個 String 物件來處理操作,處理結束後立即銷毀該物件。

    let a = "abc";
    console.log(a.length); // 3
    console.log(a.toUpperCase()); // "ABC"
    

    在這段程式碼中,總共會產生兩個 String 包裝物件,第二行和第三行各一次。

  4. 比較行為:
    在進行比較時,原始型別比較的是值,而複合型別比較的是記憶體位址。

    let a1 = 5;
    let a2 = 5;
    let b1 = { val: 5 };
    let b2 = { val: 5 };
    let b3 = b1;
    console.log(a1 == a2); // true
    console.log(b1 == b2); // false
    console.log(b1 == b3); // true
    

    字面上看起來雖然 b1 和 b2 擁有相同的屬性命名與屬性值,但實際上宣告的 val: 5 是各自儲存在不同記憶體位址。複合型別比較時以位址比較,所以才得到結果 false。但通過 = 的賦值,實際上是把 b3 也指向 b1 所指向的位址,兩者指向同一位址,因此得到結果 true。


上一篇
ECMAScript Version - ES Version
下一篇
原始型別的宣告與特性
系列文
Don't make JavaScript Just Surpise31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言